package QianFeng12;

import org.junit.Test;
import java.util.*;

/**
 * 泛型
 * T：参数化类型，在实际使用时才会指定具体的类型
 * 泛型只作用于编译期检查，在编译后，会被擦除
 *
 * 为什么需要泛型？
 * 1.当我们将依个对象放入集合中，集合不会记住此对象的类型，当再次从集合中取出此对象时，改对象的编译类型
 *      变成了Object类型，但其运行时类型依然为其本身类型。
 * 2.因此，//1处取出集合元素时需要人为的强制类型转化到具体的目标类型，且很容易出现
 *      "java.lang.ClassCastException”异常。
 * 那么有没有什么办法可以使集合能够记住集合内元素各类型，且能够达到只要编译时不出现问题，运行时就不会
 * 出现"java.langClassCastException"异常呢?      答案就是使用泛型。
 *
 * JDK 1.5之后出现了新的技术----泛型(Generic) ，此技术的最大特点是类中的属性的类型可以由外部决定。泛型,
 * 即“参数化类型”。一提到参数，最熟悉的就是定对方法时有形参，然后调用此方法时传递实参。那么参数
 * 化类型怎么理解呢?顾名思义，就是将类型由原来的具体的类型参数化，类似于方法中的变量参数，此时类
 * 型也定义成参数形式(可以称之为类型形参)，然后在使用/调用时传入具体的类型(类型实参)。
 *
 * 泛型类和方法定义:
 * class Node<T> {
 *     private T data;
 *     public Node()}
 *     public Node(T data){
 *     this.data= data;
 * }
 * public T getData() {
 *     return data;
 * }
 * public void setData(T data) {
 *     this.data= data;
 * }
 * interface Shopping<T>{public void shopping(T name);}
 * 在泛型接口、泛型类和泛型方法的定义过程中，我们常见的如T、E、K、V等形式的参数常用于表示泛型形参，
 * 由于接收来自外部使用时候传入的类型实参,从编码的角度也称为参数化类型了。
 * 泛型只是作用于代码编译阶段，在编译过程中，对于正确检验泛型结果后，会将泛型的相关信息擦除，也就是说，
 * 成功编译过后的Class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
 **/
public class GenericDemo {
    public static void main(String[] args) {
        testNode();
        List<String> list = new ArrayList<>();
        list.add("vince");
        //list.add(10);
        //list.add(new Object());

        for (String s : list) {
            //如果我们不能确定集合类型中的元素类型，那么我们需要在处理元素时
            //要判断元素类型，才能做相应的操作
            System.out.println(s);
        }
    }

    public static void testNode() {
        Node<String> stringNode = new Node<>("sun");
        Node<Integer> intNode = new Node<>(10);

        System.out.println(stringNode.getDate());
        System.out.println(intNode.getDate());
    }
    /*-----------------------------------------------------------------------------------------*/
    /**
     * 通配符
     *
     * 问题:
     * Node<Number> c1 = new Node<Number>();
     * Node<Integer> c2 = new Node<Integer>() ;
     * c1=c2;//此时无法转换
     * public static void getData(Node<Number> data){
     * System. out.println("data :" + data.getData();
     * 此时，我们可以使用通配符来解决
     * “?”表示的是可以接收任意的泛型类型，但是只是接收输出，并不能修改。
     * public static void getData(Node<?> data){
     * System.out.println("data :" + data.getData();
     * 提示:
     * 使用通配符可以引用其他各种参数化类型，通配符定义的变量主要用作引用，
     * 可以调用与参数无关的方法，不能调用与参数有关的方法。
     *
     * 泛型的上限与下限：
     * 泛型上限就指一个的操作泛型最大的操作父类，例如，现在最大的上限设置成“Number”类型，
     * 那么此时，所能够接收到的类型只能是Number及其子类(Integer) 。
     * 泛型的上限通过以下的语法完成:
     * ? extends类
     *      Node<? extends T> Node为某个类
     * 泛型的下限指的是只能设置其具体的类或者父类。设置的语法如下:
     * ? super类
     *      Node<? super T> Node为某个类
     **/
    @Test
    public void wildcard() {
        Node<Number> n1 = new Node<>(10);
        Node<Integer> n2 = new Node<>(20);
        Node<String> n3 = new Node<>("大神");
        getDate1(n1);
        //getDate(n2); 泛型不对，不能传值
        getDate2(n1);
        getDate2(n2);
        getDate2(n3);
        getUpperNumberData(n1);
        getUpperNumberData(n2);
        //getUpperNumberData(n3); 超出上限
        getLowerNumberData(n1);
        //getLowerNumberData(n2); 只能是本类或父类

    }
    public static void getDate1(Node<Number> node) {
        System.out.println(node.getDate());
    }
    public static void getDate2(Node<?> node) {
        //node.setDate(20); 错误只能接收输出，不能修改
        System.out.println(node.getDate());
    }
    public static void getUpperNumberData(Node<? extends Number> data){ //只能是Number类及其子类
        System.out.println(" data :" + data.getDate());
    }
    public static void getLowerNumberData(Node<? super Number> data){ //只能是Number类及其父类
        System.out.println(" data :" + data.getDate());
    }
    /*-----------------------------------------------------------------------------------------*/
    /**
     * 泛型方法定义使用
     **/
    @Test
    public void GenericFunction() {
        String[] arrays = {"大神", "大佬", "高手"};
        System.out.println(Arrays.toString(func(arrays, 0, 2)));
    }
    public static <T> T[] func(T[] array, int i, int t) {
        T temp = array[i];
        array[i] = array[t];
        array[t] = temp;
        return array;
    }

    /**
     * 泛型的嵌套使用
     * 在使用集合 Map的时候，我们可以这样遍历。
     * Set<Entry<Integer, String>> entry = map.entrySet();
     **/
    @Test
    public void GenericNest() {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "sun");
        map.put(2, "pig");

        Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
        for (Map.Entry entry : entrySet) {
            System.out.println(entry.getKey() + "-" + entry.getValue());
        }
    }
}
